/*
 *    Copyright 2024-2025, Warm-Flow (290631660@qq.com).
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.xygit.share.flow.core.service.impl;

import com.xygit.share.flow.core.FlowEngine;
import com.xygit.share.flow.core.constant.ExceptionCons;
import com.xygit.share.flow.core.dto.*;
import com.xygit.share.flow.core.entity.*;
import com.xygit.share.flow.core.enums.*;
import com.xygit.share.flow.core.listener.Listener;
import com.xygit.share.flow.core.listener.ListenerVariable;
import com.xygit.share.flow.core.orm.dao.FlowInstanceDao;
import com.xygit.share.flow.core.orm.service.impl.ShareServiceImpl;
import com.xygit.share.flow.core.service.InsService;
import com.xygit.share.flow.core.utils.*;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程实例Service业务层处理
 *
 * @author warm
 * @since 2023-03-29
 */
public class InsServiceImpl extends ShareServiceImpl<FlowInstanceDao<Instance>, Instance> implements InsService {

    @Override
    public InsService setDao(FlowInstanceDao<Instance> shareDao) {
        this.shareDao = shareDao;
        return this;
    }

    @Override
    public Instance start(String businessId, FlowParams flowParams) {
        return startWorkflow(businessId, null, false, flowParams);
    }

    private Instance startWorkflow(String businessId, Long instanceId, boolean restart, FlowParams flowParams) {
        Definition definition;
        if (restart) {
            // 参数校验
            AssertUtil.isFalse(ObjectUtil.isNotNull(instanceId), ExceptionCons.NULL_INSTANCE_ID);
            Instance instance = FlowEngine.insService().getById(instanceId);
            definition = FlowEngine.defService().getById(instance.getDefinitionId());
        } else {
            AssertUtil.isFalse(flowParams.getFlowCode() == null || flowParams.getDefId() == null, ExceptionCons.NULL_FLOW_CODE);
            AssertUtil.isEmpty(businessId, ExceptionCons.NULL_BUSINESS_ID);
            if (flowParams.getDefId() != null) {
                definition = FlowEngine.defService().getById(flowParams.getDefId());
            } else {
                definition = FlowEngine.defService().getPublishByFlowCode(flowParams.getFlowCode());
            }
        }
        // 获取已发布的流程节点

        AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF);
        FlowCombine flowCombine = FlowEngine.defService().getFlowCombine(definition);
        // 获取开始节点
        Node startNode = StreamUtils.filterOne(flowCombine.getAllNodes(), t -> NodeType.isStart(t.getNodeType()));
        AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE);

        // 判断流程定义是否激活状态
        AssertUtil.isTrue(definition.getActivityStatus().equals(ActivityStatus.SUSPENDED.getKey()), ExceptionCons.NOT_DEFINITION_ACTIVITY);
        flowParams.skipType(SkipType.PASS.getKey());

        // 执行开始监听器
        ListenerUtil.executeListener(new ListenerVariable(definition, null, startNode, flowParams.getVariable()).setFlowParams(flowParams), Listener.LISTENER_START);


        // 获取下一个节点，如果是网关节点，则重新获取后续节点
        PathWayData pathWayData = new PathWayData().setDefId(startNode.getDefinitionId()).setSkipType(flowParams.getSkipType());
        List<Node> nextNodes = FlowEngine.nodeService().getNextNodeList(startNode, null, flowParams.getSkipType(), flowParams.getVariable(), pathWayData, flowCombine);

        // 设置流程实例对象
        Instance instance = instanceId == null ? setStartInstance(nextNodes.get(0), businessId, flowParams)
                : setReStartInstance(nextNodes.get(0), instanceId, flowParams);

        // 设置历史任务
        HisTask hisTask = setHisTask(nextNodes, flowParams, startNode, instance.getId());

        List<Task> addTasks = StreamUtils.toList(nextNodes, node -> FlowEngine.taskService().addTask(node, instance, definition, flowParams));

        // 办理人变量替换
        if (CollUtil.isNotEmpty(addTasks)) {
            ExpressionUtil.evalVariable(addTasks, flowParams);
        }

        // 执行分派监听器
        ListenerUtil.executeListener(new ListenerVariable(definition, instance, startNode, flowParams.getVariable(), null, nextNodes, addTasks).setFlowParams(flowParams), Listener.LISTENER_ASSIGNMENT);

        // 设置流程图元数据
        pathWayData.getTargetNodes().addAll(nextNodes);
        instance.setDefJson(FlowEngine.chartService().startMetadata(pathWayData));

        // 执行分派监听器
        ListenerUtil.executeListener(new ListenerVariable(definition, instance, startNode, flowParams.getVariable()
                , null, nextNodes, addTasks).setFlowParams(flowParams), Listener.LISTENER_ASSIGNMENT);

        // 开启流程，保存流程信息
        saveFlowInfo(instance, addTasks, hisTask, flowParams, instanceId);

        // 执行完成和创建监听器
        ListenerUtil.endCreateListener(new ListenerVariable(definition, instance, startNode, flowParams.getVariable(), null, nextNodes, addTasks).setFlowParams(flowParams));
        return instance;
    }

    @Override
    public List<Instance> listByDefIds(List<Long> defIds) {
        return getDao().getByDefIds(defIds);
    }

    @Override
    public boolean remove(List<Long> instanceIds) {
        return toRemoveTask(instanceIds);
    }

    @Override
    public List<Instance> getByDefId(Long definitionId) {
        return list(FlowEngine.newIns().setDefinitionId(definitionId));
    }

    /**
     * 设置历史任务
     *
     * @param nextNodes  下一节点集合
     * @param flowParams 流程参数
     * @param startNode  开始节点
     * @param instanceId 流程实例id
     */
    private HisTask setHisTask(List<Node> nextNodes, FlowParams flowParams, Node startNode, Long instanceId) {
        Task startTask = FlowEngine.newTask().setInstanceId(instanceId).setDefinitionId(startNode.getDefinitionId()).setNodeCode(startNode.getNodeCode()).setNodeName(startNode.getNodeName()).setNodeType(startNode.getNodeType());
        FlowEngine.dataFillHandler().idFill(startTask);
        // 开始任务转历史任务
        return FlowEngine.hisTaskService().setSkipInsHis(startTask, nextNodes, flowParams);
    }

    /**
     * 开启流程，保存流程信息
     *
     * @param instance 流程实例
     * @param addTasks 新增任务
     * @param hisTask  历史任务
     */
    private void saveFlowInfo(Instance instance, List<Task> addTasks, HisTask hisTask, FlowParams flowParams, Long instanceId) {
        FlowEngine.taskService().setInsFinishInfo(instance, addTasks, flowParams);
        // 待办任务设置处理人
        if (CollUtil.isNotEmpty(addTasks)) {
            List<User> users = FlowEngine.userService().taskAddUsers(addTasks);
            FlowEngine.taskService().saveBatch(addTasks);
            FlowEngine.userService().saveBatch(users);
        }
        FlowEngine.hisTaskService().save(hisTask);
        if (instanceId == null) {
            save(instance);
        } else {
            updateById(instance);
        }
    }

    /**
     * 设置流程实例对象
     *
     * @param firstBetweenNode 第一个中间节点
     * @param businessId       业务id
     * @return Instance
     */
    private Instance setStartInstance(Node firstBetweenNode, String businessId, FlowParams flowParams) {
        Instance instance = FlowEngine.newIns();
        Date now = new Date();
        FlowEngine.dataFillHandler().idFill(instance);
        // 关联业务id,起始后面可以不用到业务id,传业务id目前来看只是为了批量创建流程的时候能创建出有区别化的流程,也是为了后期需要用到businessId。
        instance.setDefinitionId(firstBetweenNode.getDefinitionId()).setBusinessId(businessId).setNodeType(firstBetweenNode.getNodeType()).setNodeCode(firstBetweenNode.getNodeCode()).setNodeName(firstBetweenNode.getNodeName()).setFlowStatus(StringUtils.emptyDefault(flowParams.getFlowStatus(), FlowStatus.TOBESUBMIT.getKey())).setActivityStatus(ActivityStatus.ACTIVITY.getKey()).setVariable(FlowEngine.jsonConvert.objToStr(flowParams.getVariable())).setCreateTime(now).setUpdateTime(now).setCreateBy(flowParams.getHandler()).setExt(flowParams.getExt());
        return instance;
    }

    /**
     * 设置流程实例对象
     *
     * @param firstBetweenNode 第一个中间节点
     * @param instanceId       流程实例id
     * @return Instance
     */
    private Instance setReStartInstance(Node firstBetweenNode, Long instanceId, FlowParams flowParams) {
        Instance instance = getById(instanceId);
        Date now = new Date();
        // 关联业务id,起始后面可以不用到业务id,传业务id目前来看只是为了批量创建流程的时候能创建出有区别化的流程,也是为了后期需要用到businessId。
        instance.setDefinitionId(firstBetweenNode.getDefinitionId()).setBusinessId(instance.getBusinessId()).setNodeType(firstBetweenNode.getNodeType()).setNodeCode(firstBetweenNode.getNodeCode()).setNodeName(firstBetweenNode.getNodeName()).setFlowStatus(StringUtils.emptyDefault(flowParams.getFlowStatus(), FlowStatus.TOBESUBMIT.getKey())).setActivityStatus(ActivityStatus.ACTIVITY.getKey()).setVariable(FlowEngine.jsonConvert.objToStr(flowParams.getVariable())).setCreateTime(now).setUpdateTime(now).setCreateBy(flowParams.getHandler()).setExt(flowParams.getExt());
        return instance;
    }

    private boolean toRemoveTask(List<Long> instanceIds) {
        AssertUtil.isEmpty(instanceIds, ExceptionCons.NULL_INSTANCE_ID);

        List<Long> taskIds = new ArrayList<>();
        instanceIds.forEach(instanceId -> taskIds.addAll(FlowEngine.taskService().list(FlowEngine.newTask().setInstanceId(instanceId)).stream().map(Task::getId).collect(Collectors.toList())));

        if (CollUtil.isNotEmpty(taskIds)) {
            FlowEngine.userService().deleteByTaskIds(taskIds);
        }

        boolean success = FlowEngine.taskService().deleteByInsIds(instanceIds);
        if (success) {
            FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
            return FlowEngine.insService().removeByIds(instanceIds);
        }
        return false;
    }

    @Override
    public boolean active(Long id) {
        Instance instance = getById(id);
        AssertUtil.isTrue(instance.getActivityStatus().equals(ActivityStatus.ACTIVITY.getKey()), ExceptionCons.INSTANCE_ALREADY_ACTIVITY);
        instance.setActivityStatus(ActivityStatus.ACTIVITY.getKey());
        return updateById(instance);
    }

    @Override
    public boolean unActive(Long id) {
        Instance instance = getById(id);
        AssertUtil.isTrue(instance.getActivityStatus().equals(ActivityStatus.SUSPENDED.getKey()), ExceptionCons.INSTANCE_ALREADY_SUSPENDED);
        instance.setActivityStatus(ActivityStatus.SUSPENDED.getKey());
        return updateById(instance);
    }

    @Override
    public void removeVariables(Long instanceId, String... keys) {
        Instance instance = FlowEngine.insService().getById(instanceId);
        if (instance != null) {
            Map<String, Object> variableMap = instance.getVariableMap();
            for (String key : keys) {
                variableMap.remove(key);
            }
            instance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
            FlowEngine.insService().updateById(instance);
        }
    }

    @Override
    public Instance restart(Long instanceId, FlowParams flowParams) {

        Instance instance = startWorkflow(null, instanceId, true, flowParams);
        String nodeCode = flowParams.getNodeCode();
        String flowStatus = flowParams.getFlowStatus();
        String hisStatus = flowParams.getHisStatus();
        List<Task> taskList = FlowEngine.taskService().getByInsId(instance.getId());
        for (Task task : taskList) {
            task.setFlowStatus(StringUtils.emptyDefault(hisStatus, FlowStatus.TOBESUBMIT.getKey()));
        }
        instance.setNodeType(NodeType.BETWEEN.getKey());
        //获取第一个节点
        Node firstNodeCode = FlowEngine.nodeService().firstNodeCode(instance.getDefinitionId());
        if (firstNodeCode.getNodeCode().equals(nodeCode) || StringUtils.isEmpty(nodeCode)) {
            instance.setFlowStatus(StringUtils.emptyDefault(flowStatus, FlowStatus.TOBESUBMIT.getKey()));
            FlowEngine.taskService().updateBatch(taskList);
            updateById(instance);
            return instance;
        }
        instance.setFlowStatus(StringUtils.emptyDefault(flowStatus, FlowStatus.APPROVAL.getKey()));
        List<Node> nodeCodes = FlowEngine.nodeService().getByNodeCodes(Collections.singletonList(nodeCode), instance.getDefinitionId());
        AssertUtil.isTrue(CollUtil.isEmpty(nodeCodes), "重启节点不存在");

        List<Node> nodeCodeList = nodeCodes.stream()
                .filter(node -> NodeType.isBetween(node.getNodeType()))
                .collect(Collectors.toList());
        AssertUtil.isTrue(CollUtil.isEmpty(nodeCodeList), "重启节点必须是中间节点");
        flowParams.skipType(SkipType.REJECT.getKey());
        FlowEngine.taskService().skipByInsId(instanceId, flowParams);
        DefJson defJson = FlowEngine.jsonConvert.strToBean(instance.getDefJson(), DefJson.class);
        //构建节点状态
        List<NodeJson> nodeList = defJson.getNodeList();
        for (NodeJson nodeJson : nodeList) {
            nodeJson.setStatus(ChartStatus.DONE.getKey());
            if (nodeCode.equals(nodeJson.getNodeCode())) {
                nodeJson.setStatus(ChartStatus.TO_DO.getKey());
            }
        }
        String objToStr = FlowEngine.jsonConvert.objToStr(defJson);
        instance.setDefJson(objToStr);
        updateById(instance);
        List<Long> taskIdList = StreamUtils.toList(taskList, Task::getId);
        List<HisTask> hisTaskList = FlowEngine.hisTaskService().getByInsId(instanceId);
        for (HisTask hisTask : hisTaskList) {
            if (taskIdList.contains(hisTask.getTaskId())) {
                FlowEngine.hisTaskService().removeById(hisTask.getId());
            }
        }
        return instance;
    }
}
